{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b0ce4839",
   "metadata": {},
   "source": [
    "# DAY 19"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "777b0525",
   "metadata": {},
   "source": [
    "先运行之前的代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "ba853c51",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 先运行之前预处理好的代码\n",
    "import pandas as pd\n",
    "import pandas as pd    #用于数据处理和分析，可处理表格数据。\n",
    "import numpy as np     #用于数值计算，提供了高效的数组操作。\n",
    "import matplotlib.pyplot as plt    #用于绘制各种类型的图表\n",
    "import seaborn as sns   #基于matplotlib的高级绘图库，能绘制更美观的统计图形。\n",
    "import warnings\n",
    "warnings.filterwarnings(\"ignore\")\n",
    " \n",
    " # 设置中文字体（解决中文显示问题）\n",
    "plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows系统常用黑体字体\n",
    "plt.rcParams['axes.unicode_minus'] = False    # 正常显示负号\n",
    "data = pd.read_csv('data.csv')    #读取数据\n",
    "\n",
    "\n",
    "# 先筛选字符串变量 \n",
    "discrete_features = data.select_dtypes(include=['object']).columns.tolist()\n",
    "# Home Ownership 标签编码\n",
    "home_ownership_mapping = {\n",
    "    'Own Home': 1,\n",
    "    'Rent': 2,\n",
    "    'Have Mortgage': 3,\n",
    "    'Home Mortgage': 4\n",
    "}\n",
    "data['Home Ownership'] = data['Home Ownership'].map(home_ownership_mapping)\n",
    "\n",
    "# Years in current job 标签编码\n",
    "years_in_job_mapping = {\n",
    "    '< 1 year': 1,\n",
    "    '1 year': 2,\n",
    "    '2 years': 3,\n",
    "    '3 years': 4,\n",
    "    '4 years': 5,\n",
    "    '5 years': 6,\n",
    "    '6 years': 7,\n",
    "    '7 years': 8,\n",
    "    '8 years': 9,\n",
    "    '9 years': 10,\n",
    "    '10+ years': 11\n",
    "}\n",
    "data['Years in current job'] = data['Years in current job'].map(years_in_job_mapping)\n",
    "\n",
    "# Purpose 独热编码，记得需要将bool类型转换为数值\n",
    "data = pd.get_dummies(data, columns=['Purpose'])\n",
    "data2 = pd.read_csv(\"data.csv\") # 重新读取数据，用来做列名对比\n",
    "list_final = [] # 新建一个空列表，用于存放独热编码后新增的特征名\n",
    "for i in data.columns:\n",
    "    if i not in data2.columns:\n",
    "       list_final.append(i) # 这里打印出来的就是独热编码后的特征名\n",
    "for i in list_final:\n",
    "    data[i] = data[i].astype(int) # 这里的i就是独热编码后的特征名\n",
    "\n",
    "\n",
    "\n",
    "# Term 0 - 1 映射\n",
    "term_mapping = {\n",
    "    'Short Term': 0,\n",
    "    'Long Term': 1\n",
    "}\n",
    "data['Term'] = data['Term'].map(term_mapping)\n",
    "data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列\n",
    "continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist()  #把筛选出来的列名转换成列表\n",
    " \n",
    " # 连续特征用中位数补全\n",
    "for feature in continuous_features:     \n",
    "    mode_value = data[feature].mode()[0]            #获取该列的众数。\n",
    "    data[feature].fillna(mode_value, inplace=True)          #用众数填充该列的缺失值，inplace=True表示直接在原数据上修改。\n",
    "\n",
    "# 最开始也说了 很多调参函数自带交叉验证，甚至是必选的参数，你如果想要不交叉反而实现起来会麻烦很多\n",
    "# 所以这里我们还是只划分一次数据集\n",
    "from sklearn.model_selection import train_test_split\n",
    "X = data.drop(['Credit Default'], axis=1)  # 特征，axis=1表示按列删除\n",
    "y = data['Credit Default'] # 标签\n",
    "# # 按照8:2划分训练集和测试集\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  # 80%训练集，20%测试集\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "07ac6e57",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- 1. 默认参数随机森林 (训练集 -> 测试集) ---\n",
      "训练与预测耗时: 1.8811 秒\n",
      "\n",
      "默认随机森林 在测试集上的分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0       0.77      0.97      0.86      1059\n",
      "           1       0.79      0.30      0.43       441\n",
      "\n",
      "    accuracy                           0.77      1500\n",
      "   macro avg       0.78      0.63      0.64      1500\n",
      "weighted avg       0.77      0.77      0.73      1500\n",
      "\n",
      "默认随机森林 在测试集上的混淆矩阵：\n",
      "[[1023   36]\n",
      " [ 309  132]]\n"
     ]
    }
   ],
   "source": [
    "from sklearn.ensemble import RandomForestClassifier #随机森林分类器\n",
    "\n",
    "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 用于评估分类器性能的指标\n",
    "from sklearn.metrics import classification_report, confusion_matrix #用于生成分类报告和混淆矩阵\n",
    "import warnings #用于忽略警告信息\n",
    "warnings.filterwarnings(\"ignore\") # 忽略所有警告信息\n",
    "# --- 1. 默认参数的随机森林 ---\n",
    "# 评估基准模型，这里确实不需要验证集\n",
    "print(\"--- 1. 默认参数随机森林 (训练集 -> 测试集) ---\")\n",
    "import time # 这里介绍一个新的库，time库，主要用于时间相关的操作，因为调参需要很长时间，记录下会帮助后人知道大概的时长\n",
    "start_time = time.time() # 记录开始时间\n",
    "rf_model = RandomForestClassifier(random_state=42)\n",
    "rf_model.fit(X_train, y_train) # 在训练集上训练\n",
    "rf_pred = rf_model.predict(X_test) # 在测试集上预测\n",
    "end_time = time.time() # 记录结束时间\n",
    "\n",
    "print(f\"训练与预测耗时: {end_time - start_time:.4f} 秒\")\n",
    "print(\"\\n默认随机森林 在测试集上的分类报告：\")\n",
    "print(classification_report(y_test, rf_pred))\n",
    "print(\"默认随机森林 在测试集上的混淆矩阵：\")\n",
    "print(confusion_matrix(y_test, rf_pred))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a28d196",
   "metadata": {},
   "source": [
    "过去电脑性能比较差，特征数目太多计算起来很慢。同时特征中可能存在很多冗余特征干扰解释性、存在噪声特征干扰精度。\n",
    "\n",
    "所以在面对高维特征的时候常常需要引入特征降维，我们之前课程中的项目的特征也就小几十个，不太需要做降维，对于某些特征较多的数据，如基因数据、微生物数据、传感器数据等，特征较多，所以会考虑特征降维。\n",
    "\n",
    "特征降维一般有2种策略：\n",
    "1. 特征筛选：从n个特征中筛选出m个特征，比如方差筛选，剔除方差过小的特征；利用皮尔逊相关系数筛选；lasso筛选（lasso自带的系数可以理解为重要性）、利用树模型自带的重要性、shap重要性等筛选；特征递归方法\n",
    "2. 特征组合：从n个特征中组合出m个特征，如pca等\n",
    "\n",
    "今天这节先说一下特征筛选"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "035547b0",
   "metadata": {},
   "source": [
    "# 特征筛选"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67c25215",
   "metadata": {},
   "source": [
    "## 方差筛选\n",
    "\n",
    "方差筛选是一种简单而有效的特征筛选方法。它的核心逻辑是：特征的方差反映了数据的变化程度，方差很小的特征几乎没有变化，对模型的预测帮助不大。比如，一个特征的值在所有样本中几乎都一样（方差接近0），那么它对区分不同类别或预测结果几乎没有贡献。因此，方差筛选会设定一个方差阈值，剔除方差低于这个阈值的特征，保留那些变化较大的特征，从而减少特征数量，提高模型效率。\n",
    "\n",
    "这种方法特别适合处理高维数据，能快速去掉不重要的特征，但它不考虑特征与目标变量之间的关系，可能会误删一些低方差但有意义的特征。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "38fb381f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- 方差筛选 (Variance Threshold) ---\n",
      "方差筛选后保留的特征数量: 21\n",
      "保留的特征: ['Id', 'Home Ownership', 'Annual Income', 'Years in current job', 'Tax Liens', 'Number of Open Accounts', 'Years of Credit History', 'Maximum Open Credit', 'Number of Credit Problems', 'Months since last delinquent', 'Bankruptcies', 'Long Term', 'Current Loan Amount', 'Current Credit Balance', 'Monthly Debt', 'Credit Score', 'Purpose_business loan', 'Purpose_buy a car', 'Purpose_debt consolidation', 'Purpose_home improvements', 'Purpose_other']\n",
      "训练与预测耗时: 2.0066 秒\n",
      "\n",
      "方差筛选后随机森林在测试集上的分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0       0.77      0.97      0.86      1059\n",
      "           1       0.80      0.29      0.42       441\n",
      "\n",
      "    accuracy                           0.77      1500\n",
      "   macro avg       0.78      0.63      0.64      1500\n",
      "weighted avg       0.78      0.77      0.73      1500\n",
      "\n",
      "方差筛选后随机森林在测试集上的混淆矩阵：\n",
      "[[1028   31]\n",
      " [ 315  126]]\n"
     ]
    }
   ],
   "source": [
    "# 打印标题，表明这是方差筛选的部分\n",
    "print(\"--- 方差筛选 (Variance Threshold) ---\")\n",
    "\n",
    "# 导入需要的工具库\n",
    "from sklearn.feature_selection import VarianceThreshold  # 方差筛选工具，用于剔除方差小的特征\n",
    "import time  # 用于记录代码运行时间，方便比较效率\n",
    "\n",
    "# 记录开始时间，后面会计算整个过程耗时\n",
    "start_time = time.time()\n",
    "\n",
    "# 创建方差筛选器，设置方差阈值为0.01\n",
    "# 阈值是指方差的最小值，低于这个值的特征会被删除（可以根据数据情况调整阈值）\n",
    "selector = VarianceThreshold(threshold=0.01)\n",
    "\n",
    "# 对训练数据进行方差筛选，fit_transform会计算每个特征的方差并剔除不满足阈值的特征\n",
    "# X_train是原始训练数据，X_train_var是筛选后的训练数据\n",
    "X_train_var = selector.fit_transform(X_train)\n",
    "\n",
    "# 对测试数据应用同样的筛选规则，transform会直接用训练数据的筛选结果处理测试数据\n",
    "# X_test是原始测试数据，X_test_var是筛选后的测试数据\n",
    "X_test_var = selector.transform(X_test)\n",
    "\n",
    "# 获取被保留下来的特征名称\n",
    "# selector.get_support()返回一个布尔值列表，表示哪些特征被保留，这个是selector这个实例化的类的一个方法\n",
    "# X_train.columns是特征的名称，结合布尔值列表可以提取保留特征的名字\n",
    "selected_features_var = X_train.columns[selector.get_support()].tolist()\n",
    "\n",
    "# 打印筛选后保留的特征数量和具体特征名称，方便查看结果\n",
    "print(f\"方差筛选后保留的特征数量: {len(selected_features_var)}\")\n",
    "print(f\"保留的特征: {selected_features_var}\")\n",
    "\n",
    "# 创建一个随机森林分类模型，用于在筛选后的数据上进行训练和预测\n",
    "# random_state=42是为了保证每次运行结果一致，方便教学和对比\n",
    "rf_model_var = RandomForestClassifier(random_state=42)\n",
    "\n",
    "# 在筛选后的训练数据上训练模型\n",
    "# X_train_var是筛选后的特征数据，y_train是对应的目标标签\n",
    "rf_model_var.fit(X_train_var, y_train)\n",
    "\n",
    "# 使用训练好的模型对筛选后的测试数据进行预测\n",
    "# X_test_var是筛选后的测试特征数据，rf_pred_var是预测结果\n",
    "rf_pred_var = rf_model_var.predict(X_test_var)\n",
    "\n",
    "# 记录结束时间，计算整个训练和预测过程的耗时\n",
    "end_time = time.time()\n",
    "print(f\"训练与预测耗时: {end_time - start_time:.4f} 秒\")\n",
    "\n",
    "# 打印模型在测试集上的分类报告，展示模型的性能\n",
    "# 分类报告包括精确率、召回率、F1分数等指标，帮助评估模型好坏\n",
    "print(\"\\n方差筛选后随机森林在测试集上的分类报告：\")\n",
    "print(classification_report(y_test, rf_pred_var))\n",
    "\n",
    "# 打印混淆矩阵，展示模型预测的详细结果\n",
    "# 混淆矩阵显示了真实标签和预测标签的对应情况，比如多少样本被正确分类，多少被错分\n",
    "print(\"方差筛选后随机森林在测试集上的混淆矩阵：\")\n",
    "print(confusion_matrix(y_test, rf_pred_var))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5f3839e",
   "metadata": {},
   "source": [
    "## 皮尔逊相关系数筛选\n",
    "\n",
    "皮尔逊相关系数筛选是一种基于特征与目标变量之间相关性的特征选择方法。它的核心逻辑是：计算每个特征与目标变量之间的相关系数（范围在-1到1之间，值越大表示正相关越强，值越小表示负相关越强，接近0表示几乎无关），然后根据相关系数的绝对值大小，选择与目标变量相关性较高的特征，剔除相关性较低的特征。这种方法适用于目标变量是连续型的情况（如果是分类问题，可以先对目标变量编码）。通过皮尔逊相关系数筛选，我们可以保留那些对预测目标最有帮助的特征，减少无关或冗余特征的干扰。\n",
    "\n",
    "皮尔逊相关系数筛选法是一种基于变量相关性的经典特征选择技术，常用于处理目标变量为连续型的场景。若面对分类问题，通常需要先对目标变量进行编码处理，将其转化为数值型数据后再开展分析。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "edd0a618",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- 皮尔逊相关系数筛选 ---\n",
      "皮尔逊相关系数筛选后保留的特征数量: 10\n",
      "保留的特征: ['Id', 'Home Ownership', 'Annual Income', 'Tax Liens', 'Number of Open Accounts', 'Long Term', 'Current Loan Amount', 'Credit Score', 'Purpose_business loan', 'Purpose_small business']\n",
      "训练与预测耗时: 1.5516 秒\n",
      "\n",
      "皮尔逊相关系数筛选后随机森林在测试集上的分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0       0.77      0.94      0.84      1059\n",
      "           1       0.67      0.31      0.42       441\n",
      "\n",
      "    accuracy                           0.75      1500\n",
      "   macro avg       0.72      0.62      0.63      1500\n",
      "weighted avg       0.74      0.75      0.72      1500\n",
      "\n",
      "皮尔逊相关系数筛选后随机森林在测试集上的混淆矩阵：\n",
      "[[991  68]\n",
      " [304 137]]\n"
     ]
    }
   ],
   "source": [
    "print(\"--- 皮尔逊相关系数筛选 ---\")\n",
    "from sklearn.feature_selection import SelectKBest, f_classif\n",
    "import time\n",
    "\n",
    "start_time = time.time()\n",
    "\n",
    "# 计算特征与目标变量的相关性，选择前k个特征（这里设为10个，可调整）\n",
    "# 注意：皮尔逊相关系数通常用于回归问题（连续型目标变量），但如果目标是分类问题，可以用f_classif\n",
    "k = 10\n",
    "selector = SelectKBest(score_func=f_classif, k=k)\n",
    "X_train_corr = selector.fit_transform(X_train, y_train)\n",
    "X_test_corr = selector.transform(X_test)\n",
    "\n",
    "# 获取筛选后的特征名\n",
    "selected_features_corr = X_train.columns[selector.get_support()].tolist()\n",
    "print(f\"皮尔逊相关系数筛选后保留的特征数量: {len(selected_features_corr)}\")\n",
    "print(f\"保留的特征: {selected_features_corr}\")\n",
    "\n",
    "# 训练随机森林模型\n",
    "rf_model_corr = RandomForestClassifier(random_state=42)\n",
    "rf_model_corr.fit(X_train_corr, y_train)\n",
    "rf_pred_corr = rf_model_corr.predict(X_test_corr)\n",
    "\n",
    "end_time = time.time()\n",
    "print(f\"训练与预测耗时: {end_time - start_time:.4f} 秒\")\n",
    "print(\"\\n皮尔逊相关系数筛选后随机森林在测试集上的分类报告：\")\n",
    "print(classification_report(y_test, rf_pred_corr))\n",
    "print(\"皮尔逊相关系数筛选后随机森林在测试集上的混淆矩阵：\")\n",
    "print(confusion_matrix(y_test, rf_pred_corr))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec5e739a",
   "metadata": {},
   "source": [
    "## lasso筛选（基于L1正则化）\n",
    "\n",
    "Lasso回归（Least Absolute Shrinkage and Selection Operator）是一种结合特征选择和模型训练的方法。它的核心逻辑是：在进行线性回归的同时，通过引入L1正则化项（即惩罚项），强制将一些不重要特征的回归系数压缩到0，从而实现特征筛选。换句话说，Lasso会自动“挑选”对预测目标有贡献的特征（系数不为0），而剔除无关或冗余的特征（系数为0）。这种方法特别适合处理高维数据，可以减少特征数量，提高模型的解释性和计算效率。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f9dbaaf3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Lasso筛选 (L1正则化) ---\n",
      "Lasso筛选后保留的特征数量: 7\n",
      "保留的特征: ['Home Ownership', 'Years in current job', 'Number of Open Accounts', 'Years of Credit History', 'Months since last delinquent', 'Long Term', 'Credit Score']\n",
      "训练与预测耗时: 0.8391 秒\n",
      "\n",
      "Lasso筛选后随机森林在测试集上的分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0       0.77      0.94      0.85      1059\n",
      "           1       0.70      0.34      0.45       441\n",
      "\n",
      "    accuracy                           0.76      1500\n",
      "   macro avg       0.74      0.64      0.65      1500\n",
      "weighted avg       0.75      0.76      0.73      1500\n",
      "\n",
      "Lasso筛选后随机森林在测试集上的混淆矩阵：\n",
      "[[995  64]\n",
      " [293 148]]\n"
     ]
    }
   ],
   "source": [
    "print(\"--- Lasso筛选 (L1正则化) ---\")\n",
    "from sklearn.linear_model import Lasso\n",
    "from sklearn.feature_selection import SelectFromModel\n",
    "import time\n",
    "\n",
    "start_time = time.time()\n",
    "\n",
    "# 使用Lasso回归进行特征筛选\n",
    "lasso = Lasso(alpha=0.01, random_state=42)  # alpha值可调整\n",
    "selector = SelectFromModel(lasso)\n",
    "selector.fit(X_train, y_train)\n",
    "X_train_lasso = selector.transform(X_train)\n",
    "X_test_lasso = selector.transform(X_test)\n",
    "\n",
    "# 获取筛选后的特征名\n",
    "selected_features_lasso = X_train.columns[selector.get_support()].tolist()\n",
    "print(f\"Lasso筛选后保留的特征数量: {len(selected_features_lasso)}\")\n",
    "print(f\"保留的特征: {selected_features_lasso}\")\n",
    "\n",
    "# 训练随机森林模型\n",
    "rf_model_lasso = RandomForestClassifier(random_state=42)\n",
    "rf_model_lasso.fit(X_train_lasso, y_train)\n",
    "rf_pred_lasso = rf_model_lasso.predict(X_test_lasso)\n",
    "\n",
    "end_time = time.time()\n",
    "print(f\"训练与预测耗时: {end_time - start_time:.4f} 秒\")\n",
    "print(\"\\nLasso筛选后随机森林在测试集上的分类报告：\")\n",
    "print(classification_report(y_test, rf_pred_lasso))\n",
    "print(\"Lasso筛选后随机森林在测试集上的混淆矩阵：\")\n",
    "print(confusion_matrix(y_test, rf_pred_lasso))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b6fdb95",
   "metadata": {},
   "source": [
    "这个时候要注意，lasso本质上是回归模型，实际上用这个方法来筛选也是用回归模型对分类问题建模结束了，然后打印特征重要度，她是把0和1目标变量视为连续值来进行回归的。效果会差一点，不符合逻辑，但是确实可以计算"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cee1a88a",
   "metadata": {},
   "source": [
    "## 树模型重要性"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "d083c718",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- 树模型自带的重要性筛选 ---\n",
      "树模型重要性筛选后保留的特征数量: 11\n",
      "保留的特征: ['Id', 'Annual Income', 'Years in current job', 'Number of Open Accounts', 'Years of Credit History', 'Maximum Open Credit', 'Months since last delinquent', 'Current Loan Amount', 'Current Credit Balance', 'Monthly Debt', 'Credit Score']\n",
      "训练与预测耗时: 4.3868 秒\n",
      "\n",
      "树模型重要性筛选后随机森林在测试集上的分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0       0.76      0.97      0.85      1059\n",
      "           1       0.79      0.27      0.40       441\n",
      "\n",
      "    accuracy                           0.76      1500\n",
      "   macro avg       0.78      0.62      0.63      1500\n",
      "weighted avg       0.77      0.76      0.72      1500\n",
      "\n",
      "树模型重要性筛选后随机森林在测试集上的混淆矩阵：\n",
      "[[1027   32]\n",
      " [ 321  120]]\n"
     ]
    }
   ],
   "source": [
    "print(\"--- 树模型自带的重要性筛选 ---\")\n",
    "from sklearn.feature_selection import SelectFromModel\n",
    "import time\n",
    "\n",
    "start_time = time.time()\n",
    "\n",
    "# 使用随机森林的特征重要性进行筛选\n",
    "rf_selector = RandomForestClassifier(random_state=42)\n",
    "rf_selector.fit(X_train, y_train)\n",
    "selector = SelectFromModel(rf_selector, threshold=\"mean\")  # 阈值设为平均重要性，可调整\n",
    "X_train_rf = selector.transform(X_train)\n",
    "X_test_rf = selector.transform(X_test)\n",
    "\n",
    "# 获取筛选后的特征名\n",
    "selected_features_rf = X_train.columns[selector.get_support()].tolist()\n",
    "print(f\"树模型重要性筛选后保留的特征数量: {len(selected_features_rf)}\")\n",
    "print(f\"保留的特征: {selected_features_rf}\")\n",
    "\n",
    "# 训练随机森林模型\n",
    "rf_model_rf = RandomForestClassifier(random_state=42)\n",
    "rf_model_rf.fit(X_train_rf, y_train)\n",
    "rf_pred_rf = rf_model_rf.predict(X_test_rf)\n",
    "\n",
    "end_time = time.time()\n",
    "print(f\"训练与预测耗时: {end_time - start_time:.4f} 秒\")\n",
    "print(\"\\n树模型重要性筛选后随机森林在测试集上的分类报告：\")\n",
    "print(classification_report(y_test, rf_pred_rf))\n",
    "print(\"树模型重要性筛选后随机森林在测试集上的混淆矩阵：\")\n",
    "print(confusion_matrix(y_test, rf_pred_rf))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68b4fd9c",
   "metadata": {},
   "source": [
    "## SHAP重要性筛选"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "7fddf715",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- SHAP重要性筛选 ---\n",
      "SHAP重要性筛选后保留的特征数量: 10\n",
      "保留的特征: ['Id', 'Number of Open Accounts', 'Current Credit Balance', 'Years of Credit History', 'Monthly Debt', 'Maximum Open Credit', 'Long Term', 'Annual Income', 'Current Loan Amount', 'Credit Score']\n",
      "训练与预测耗时: 321.1299 秒\n",
      "\n",
      "SHAP重要性筛选后随机森林在测试集上的分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0       0.77      0.95      0.85      1059\n",
      "           1       0.74      0.31      0.44       441\n",
      "\n",
      "    accuracy                           0.76      1500\n",
      "   macro avg       0.75      0.63      0.64      1500\n",
      "weighted avg       0.76      0.76      0.73      1500\n",
      "\n",
      "SHAP重要性筛选后随机森林在测试集上的混淆矩阵：\n",
      "[[1010   49]\n",
      " [ 304  137]]\n"
     ]
    }
   ],
   "source": [
    "print(\"--- SHAP重要性筛选 ---\")\n",
    "import shap\n",
    "from sklearn.feature_selection import SelectKBest\n",
    "import time\n",
    "\n",
    "start_time = time.time()\n",
    "\n",
    "# 使用随机森林模型计算SHAP值\n",
    "rf_shap = RandomForestClassifier(random_state=42)\n",
    "rf_shap.fit(X_train, y_train)\n",
    "explainer = shap.TreeExplainer(rf_shap)\n",
    "shap_values = explainer.shap_values(X_train)\n",
    "\n",
    "# 计算每个特征的平均SHAP值（取绝对值的平均）\n",
    "mean_shap = np.abs(shap_values[1]).mean(axis=0)  # shap_values[1]对应正类\n",
    "k = 10  # 选择前10个特征，可调整\n",
    "top_k_indices = np.argsort(mean_shap)[-k:]\n",
    "X_train_shap = X_train.iloc[:, top_k_indices]\n",
    "X_test_shap = X_test.iloc[:, top_k_indices]\n",
    "\n",
    "# 获取筛选后的特征名\n",
    "selected_features_shap = X_train.columns[top_k_indices].tolist()\n",
    "print(f\"SHAP重要性筛选后保留的特征数量: {len(selected_features_shap)}\")\n",
    "print(f\"保留的特征: {selected_features_shap}\")\n",
    "\n",
    "# 训练随机森林模型\n",
    "rf_model_shap = RandomForestClassifier(random_state=42)\n",
    "rf_model_shap.fit(X_train_shap, y_train)\n",
    "rf_pred_shap = rf_model_shap.predict(X_test_shap)\n",
    "\n",
    "end_time = time.time()\n",
    "print(f\"训练与预测耗时: {end_time - start_time:.4f} 秒\")\n",
    "print(\"\\nSHAP重要性筛选后随机森林在测试集上的分类报告：\")\n",
    "print(classification_report(y_test, rf_pred_shap))\n",
    "print(\"SHAP重要性筛选后随机森林在测试集上的混淆矩阵：\")\n",
    "print(confusion_matrix(y_test, rf_pred_shap))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84ffb7b4",
   "metadata": {},
   "source": [
    "## 递归特征消除RFE\n",
    "递归特征消除（Recursive Feature Elimination, 简称RFE）是一种特征选择方法，广泛用于机器学习中，特别是在分类和回归问题中，用于从一组特征中筛选出对模型性能贡献最大的子集。RFE的核心思想是通过递归地移除最不重要的特征，逐步缩小特征集，直到达到预设的特征数量或满足其他停止条件。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "41ef62bc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- 递归特征消除 (RFE) ---\n",
      "RFE筛选后保留的特征数量: 10\n",
      "保留的特征: ['Id', 'Annual Income', 'Number of Open Accounts', 'Years of Credit History', 'Maximum Open Credit', 'Months since last delinquent', 'Current Loan Amount', 'Current Credit Balance', 'Monthly Debt', 'Credit Score']\n",
      "训练与预测耗时: 48.9730 秒\n",
      "\n",
      "RFE筛选后随机森林在测试集上的分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0       0.76      0.97      0.85      1059\n",
      "           1       0.80      0.27      0.40       441\n",
      "\n",
      "    accuracy                           0.76      1500\n",
      "   macro avg       0.78      0.62      0.63      1500\n",
      "weighted avg       0.77      0.76      0.72      1500\n",
      "\n",
      "RFE筛选后随机森林在测试集上的混淆矩阵：\n",
      "[[1030   29]\n",
      " [ 324  117]]\n"
     ]
    }
   ],
   "source": [
    "print(\"--- 递归特征消除 (RFE) ---\")\n",
    "from sklearn.feature_selection import RFE\n",
    "import time\n",
    "\n",
    "start_time = time.time()\n",
    "\n",
    "# 使用随机森林作为基础模型进行RFE\n",
    "base_model = RandomForestClassifier(random_state=42)\n",
    "rfe = RFE(base_model, n_features_to_select=10)  # 选择10个特征，可调整\n",
    "rfe.fit(X_train, y_train)\n",
    "X_train_rfe = rfe.transform(X_train)\n",
    "X_test_rfe = rfe.transform(X_test)\n",
    "\n",
    "# 获取筛选后的特征名\n",
    "selected_features_rfe = X_train.columns[rfe.support_].tolist()\n",
    "print(f\"RFE筛选后保留的特征数量: {len(selected_features_rfe)}\")\n",
    "print(f\"保留的特征: {selected_features_rfe}\")\n",
    "\n",
    "# 训练随机森林模型\n",
    "rf_model_rfe = RandomForestClassifier(random_state=42)\n",
    "rf_model_rfe.fit(X_train_rfe, y_train)\n",
    "rf_pred_rfe = rf_model_rfe.predict(X_test_rfe)\n",
    "\n",
    "end_time = time.time()\n",
    "print(f\"训练与预测耗时: {end_time - start_time:.4f} 秒\")\n",
    "print(\"\\nRFE筛选后随机森林在测试集上的分类报告：\")\n",
    "print(classification_report(y_test, rf_pred_rfe))\n",
    "print(\"RFE筛选后随机森林在测试集上的混淆矩阵：\")\n",
    "print(confusion_matrix(y_test, rf_pred_rfe))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2590baad",
   "metadata": {},
   "source": [
    "上面这些方法的计算耗时没意义，目的是筛选出最后用的特征，可以看到只保留这几个特征效果仍然很好。说明可以在未来的建模中减少计算资源"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "vs",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
